# VUE Forms
- -> encapsulate all forms into components.
- buld reusable components
- default for input is
textif nothing is declared
2 way to get data from input:
- use
v-model-> has 2-way binding - or
@input-event-listener (might be good for validation)
# v-model
v-bind, creates a one-way binding, from the data to the template.
v-model is creating two-way data binding.
v-model is a combination of :value and @input
<input type="text" :value="name" @input="setName"/>
- two-way data binding
<label>EMAIL</label>
<input type="email" required v-model="email" />
<label>Role:</label>
<select v-model="role">
<option value="developer">Web Developer</option>
<option value="designer">Web Designer</option>
</select>
# checkbox
<div class="terms">
<input type="checkbox" require v-model="terms" />
<label>Accept terms and conditions</label>
</div>
# multiple checkboxes with same v-model
- adds value to the array
<div>
<input type="checkbox" value="shaun" v-model="names" />
<label>Shaun</label>
</div>
<div>
<input type="checkbox" value="yoshi" v-model="names" />
<label>yoshi</label>
</div>
<div>
<input type="checkbox" value="mario" v-model="names" />
<label>mario</label>
</div>
# keyboard
-> access to event
<input type="text" v-model="tempSkill" @keyup="addSkill" />
//
methods: {
addSkill(event) {
console.log(event);
},
},
# v-model in Depth
this
<RegisterUserForm
v-model=form
/>
is the same as:
<RegisterUserForm
:model-value="form"
@update:model-value="(newForm) => (form = newForm)"
/>
# useVModel - vueUse
gets rid of a bunch of boiler-plate code:
<script setup>
import { useVModel } from '@vueuse/core'
const props = defineProps({
count: Number,
})
const emit = defineEmits(['update:count'])
const count = useVModel(props, 'count', emit)
</script>
<template>
<div>
<button @click="count = count - 1">-</button>
<button @click="count = 0">Reset to 0</button>
<button @click="count = count + 1">+</button>
</div>
</template>
first define the prop we want to attach to the v-model:
const props = defineProps({
count: Number,
})
Then we emit an event that uses the v-model naming convention of update:<propName>:
const emit = defineEmits(['update:count'])
Now we can use the useVModel composable to bind the prop and event to a ref:
const count = useVModel(props, 'count', emit)
This count ref will change whenever the prop changes. But whenever it’s changed from this component, it will emit the update:count event to trigger the update through the v-model directive.
We can use this Input component like this:
<script setup>
import { ref } from 'vue'
import Input from './components/Input.vue'
const count = ref(50)
</script>
<template>
<div>
<Input v-model:count="count" />
{{ count }}
</div>
</template>
The count ref here is synced to the count ref inside of the Input component through the v-model binding.
Check out the docs for useVModel here. (opens new window)
# another example:
<script setup>
import { useVModel } from '@vueuse/core'
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
})
const emit = defineEmits(['update:model-value'])
const form = useVModel(props, 'modelValue', emit)
</script>
<template>
<q-form class="q-gutter-y-md">
<q-input v-model="form.name" filled label="Name" />
<q-input v-model="form.email" filled label="Email" />
<q-select
v-model="form.fav_potato"
filled
:options="['Yukon Gold', 'Russet', 'Laura', 'Vitelotte', 'Kennebec']"
label="Favourite Type Of Potato"
/>
</q-form>
</template>
<style lang="scss" scoped></style>
parent:
<script setup>
import { ref } from 'vue'
import RegisterUserForm from './RegisterUserForm.vue'
const form = ref({
name: '',
email: '',
fav_potato: '',
})
</script>
<template>
<q-page padding>
<q-card style="max-width: 550px">
<q-card-section>
<RegisterUserForm
v-model="form"
/>
</q-card-section>
</q-card>
</q-page>
</template>
# v-model-Modifiers
# .number
-> forces a number
v-model.number is a modifier that typecasts the value as a number.
even with
type="number", the value of HTML input elements always returns a string.<input id="age" name="age" type="number" />if you use a ref, no automaric type-conversion takes place
But with vue, this returns a number - with vue3:
<input v-model.number="age" type="number" />
other modifiers:
# .lazy
- don't update immedialtely
# .trim
trims extra whitespace on beginning/end (like using the trim() -function)
# Form-Elements
# Dropdowns
v-model works on <select>
<div >
<label for="referrer">How did you hear about us?</label>
<select id="referrer" name="referrer" v-model="referrer">
<option value="google">Google</option>
<option value="wom">Word of mouth</option>
<option value="newspaper">Newspaper</option>
</select>
</div>
# Checkboxes
for a single checkbox
<input
type="checkbox"
id="confirm-terms"
name="confirm-terms"
v-model="confirm"
/>
-> you get true or false
add a
value-attribute andv-modelto every checkboxuse an empty array for the data -> you get an array with the value of all the options
<div >
<h2>What are you interested in?</h2>
<div>
<input
id="interest-news"
name="interest"
type="checkbox"
v-model="interest"
value="News"
/>
<label for="interest-news">News</label>
</div>
<div>
<input
id="interest-tutorials"
name="interest"
type="checkbox"
v-model="interest"
value="Tutorials"
/>
<label for="interest-tutorials">Tutorials</label>
</div>
<div>
<input
id="interest-nothing"
name="interest"
type="checkbox"
v-model="interest"
value="Nothing"
/>
<label for="interest-nothing">Nothing</label>
</div>
</div>
Checkboxes have a few quirks.
checkbox inputs bind their state to a
checkedproperty, not directly tovalue.The property
valueof checkboxes is usually not used on the frontend - its main purpose is to provide a value when submitted to the backend via a submit button. If omitted, thisvaluedefaults tooninputs with type
checkboxdon’t triggerinputevents, butchangeevents whenever they are selected and unselected.For checkboxes we are not emitting the target’s value through
$event.target.value, but instead the checked status through$event.target.checked.
<input
type="checkbox"
:checked="modelvalue"
class="field"
@change="$emit('update:modelValue', $event.target.checked)"
/>
<label v-if="label">{{ label }}</label>
# Radio Buttons
# BaseRadio
Radio buttons in HTML have a unique feature that we need to be aware of.
they do not work as a single input, like a checkbox would. They are part of a group of radio buttons that have a single state.
Depending on the group’s state, a radio button may be active or inactive in relation to those in its own group.
the
BaseRadiocomponent will also have another component to group them, theBaseRadioGroup.Similarly to checkboxes, radio buttons don’t bind to the
valueproperty, but use thecheckedproperty instead.
<template>
<input
type="radio"
:checked="modelValue === value"
:value="value"
@change="$emit('update:modelValue', value)"
v-bind="$attrs"
/>
<label v-if="label">{{ label }}</label>
</template>
<script>
export default {
props: {
label: {
type: String,
default: '',
},
modelValue: {
type: [String, Number],
default: '',
},
value: {
type: [String, Number],
required: true,
},
},
}
</script>
# BaseRadioGroup
you almost always want to provide at least two radios per each group
- we always want to have our
BaseRadioelements grouped, it makes sense to create a component that contains the logic for this grouping: theBaseRadioGroup.
<component
v-for="option in options"
:key="option.value"
:is="vertical ? 'div' : 'span'"
>
<BaseRadio
:label="option.label"
:value="option.value"
:name="name"
:modelValue="modelValue"
@update:modelValue="$emit('update:modelValue', $event)"
/>
</component>
# Select
- Select elements fire a
changeevent whenever the user makes a new selection
Since
$attrsis an object, we can use the JavaScript spread operator to combine our binds into a single object. We will first spread the$attrsinto our v-bind, and then bind thechangeevent into ourv-bind.
<select
:value="modelValue"
class="field"
v-bind="{
...$attrs,
onChange: ($event) => {
$emit('update:modelValue'), $event.target.value
},
}"
>
In Vue 3: remember that if we choose not to use the
@sign syntax, the event will be prefixed by the keywordon, in this caseonChangesince were listening to thechangeevent.All event listeners that are received in
$attrsfrom the parent are prefixed with theonkeyword, and the first letter is capitalized.@change = onChange
@input = onInput
# Form Validation
validate on
- submit method
- every keystroke
- wehenever an element looses focus ('blur')
@blur="validateInput"
<div class="form-control">
<label for="user-name">Your Name</label>
<input
id="user-name"
name="user-name"
type="text"
v-model.trim="userName"
@blur="validateInput"
/>
<p v-if="usernameValidity === 'invalid'">Please enter a valid name!</p>
</div>
...
validateInput() {
if (this.userName === '') {
this.usernameValidity = 'invalid';
} else {
this.usernameValidity = 'valid';
}
}
# Build Custom Element
# Binding to the value
you can use v-model on custom components. it uses a special prop "modelValue" and a special event "update:modelValue"
use props: ['modelValue'],
props: ['modelValue'],
emits: ['update:modelValue'],
in the custom element: eg.
# modelValue
By default in Vue 3, v-model expects a property named modelValue to be on your v-model-capable component.
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
data() {
return {
activeOption: this.modelValue
};
},
methods: {
activate(option) {
this.activeOption = option;
this.$emit('update:modelValue', option);
}
}
};
another example:
<template>
<label v-if="label">{{ label }}</label>
<input
:value="modelValue"
:placeholder="label"
class="field"
>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ''
},
modelValue: {
type: [String, Number],
default: ''
}
}
}
</script>
# Emitting update:modelValue
All components that are capable of being v-modeled have to emit an event in order for the parent to be able to catch the updates to that component’s data.
In Vue 3, by default all v-model contracts expect for your component to emit an update:modelValue event
<input
:placeholder="label"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
# Passing $attrs
for: Attributes, classes & styles
when you pass down attributes, classes and styles from a parent to a child, Vue will attempt to automatically figure out where inside your template these attributes should be injected.
In single root components (components with a single wrapping element), Vue will inject all the attributes, classes and styles into the root element.
In multi-root components, Vue can’t figure out which one of the nodes, or fragments, it should inject the attributes to.
-> you have to manually bind the
$attrsobject to the element.
<input
v-bind="$attrs"
class="field"
:placeholder="label"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>